using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace LightPersist.Metainfo
{
	public class PropertyMetainfo : TypeResolver
	{
		public const string UntypedBehaviourName = "UntypedBehaviour";
		bool? _isReadonly;
		ReferenceType _refType;

		public ClassMetainfo DeclaringClass { get; internal set; }

		[DefaultValue(UntypedPropertyBehaviour.Unknown)]
		public UntypedPropertyBehaviour UntypedBehaviour { get; set; }

		/// <summary>
		/// Initialized on runtime domain prepare.
		/// </summary>
		public PropertyKind Kind { get; internal set; }

		/// <summary>
		/// Detect IsUntyped using Kind. Only for runtime, do not call from code generator!
		/// </summary>
		public bool IsUntyped
		{
			get { return Kind == PropertyKind.Untyped; }
		}

		public string Name { set; get; }

		[DefaultValue(false)]
		public bool IsList { set; get; }

		[DefaultValue(ReferenceType.Unknown)]
		public ReferenceType RefType
		{
			set
			{
				// Debugger.Break();
				_refType = value;
			}
			get
			{
				if (IsInverseOf)
				{
					return ReferenceType.Weak;
				}
				return _refType == ReferenceType.Unknown ? ReferenceType.Strong : _refType;
			}
		}

		[DefaultValue(null)]
		public string InverseOf { set; get; }

		public bool IsInverseOf
		{
			get { return !string.IsNullOrEmpty(InverseOf); }
		}

		[DefaultValue(false)]
		public bool IsReadonly
		{
			get
			{
				// by default inverse-of is readonly
				if (!_isReadonly.HasValue && IsInverseOf)
				{
					return true;
				}

				// by default 'is readonly' are false
				return _isReadonly.HasValue ? _isReadonly.Value : false;
			}
			set { _isReadonly = value; }
		}

		public string Type
		{
			get { return TypeName; }
			set { TypeName = value; }
		}

		/// <summary>
		/// Detect IsModel using Kind and UntypedBehaviour. Only for runtime, do not call from code generator!
		/// </summary>
		public bool IsModelProperty
		{
			get
			{
				switch (Kind)
				{
					case PropertyKind.Untyped:
						switch (UntypedBehaviour)
						{
							case UntypedPropertyBehaviour.AllowModel:
								return true;
							case UntypedPropertyBehaviour.PrimitiveOnly:
								return false;
							case UntypedPropertyBehaviour.Unknown:
							default:
								throw new ApplicationException("UntypedBehaviour unknown: {0}, PropertyType: {1}, Name: {2}".Arg(UntypedBehaviour, TypeName, Name));
						}
					case PropertyKind.Primitive:
						return false;
					case PropertyKind.Model:
						return true;
					case PropertyKind.Unknown:
					default:
						throw new ApplicationException("PropertyKind unknown: {0}, PropertyType: {1}, Name: {2}".Arg(Kind, TypeName, Name));
				}
				//return domainMetainfo.FindClass(propMeta.TypeName) != null || IsSystemModelType(propMeta.TypeName);
			}
		}

		/// <summary>
		/// Perform property state validation
		/// </summary>
		/// <returns>null - ok, otherwise - description</returns>
		public string Validate()
		{
			if (_refType!=ReferenceType.Unknown && _refType != RefType)
			{
				// Debugger.Break();
				return "'RefType' is allways 'Weak' for 'InverseOf'";
			}
			if(IsInverseOf)
			{
				var targetClass = DeclaringClass.DeclaringDomain.FindClass(Type) ?? DeclaringClass.DeclaringDomain.FindClass(TypeNameFull);
				if(targetClass == null)
				{
					throw new ApplicationException("Can not validate property {0}.{1}: InverseOf target type '{2}' does not found".Arg(DeclaringClass.Name, Name, Type));
				}
				var targetProperty = targetClass.PropertiesAll.FirstOrDefault(x => x.Name == InverseOf);
				if(targetProperty == null)
				{
					throw new ApplicationException("Can not validate property {0}.{1}: InverseOf target property '{2}' does not found".Arg(DeclaringClass.Name, Name, InverseOf));
				}
				if(targetProperty.IsInverseOf)
				{
					throw new ApplicationException("Invalid property {0}.{1}: InverseOf target property '{2}' is marked as InverseOf too".Arg(DeclaringClass.Name, Name, InverseOf));
				}
			}
			return null;
		}

		public override string ToString()
		{
			return Name;
		}
	}
}